package xormplus

import (
	"errors"
	"gitee.com/hongzhaomin/hzm-common-go/easylog"
	"gitee.com/hongzhaomin/hzm-common-go/toolkit"
	"gitee.com/hongzhaomin/xormplus/base"
	"gitee.com/hongzhaomin/xormplus/wrappers"
	"slices"
	"xorm.io/builder"
	"xorm.io/xorm"
	"xorm.io/xorm/schemas"
)

// Dao 数据库持久层接口
type Dao interface {
	dao()
	GetEngine() xorm.Interface
	SetLog(logger easylog.Logger)
}

var _ Dao = (*BaseDao[base.Domain])(nil)

type BaseDao[Model base.Domain] struct {
	Dao
	logger easylog.Logger
}

// ================================= 新增 =================================

func (my *BaseDao[Model]) child() Dao {
	if my.Dao != nil {
		return my.Dao
	}
	return my
}

func (my *BaseDao[Model]) log() easylog.Logger {
	if my.logger != nil {
		return my.logger
	}
	return logger()
}

func (my *BaseDao[Model]) GetEngine() xorm.Interface {
	engine := my.getEngine()
	// 优先获取事务session
	session := getGoSessionByEngine(engine)
	if session != nil {
		return session
	}
	return engine
}

func (my *BaseDao[Model]) SetLog(logger easylog.Logger) {
	if logger != nil {
		my.logger = logger
	}
}

func (my *BaseDao[Model]) getEngine() *xorm.Engine {
	var model Model
	return GetDataSourceById(base.Domain.GetDataSourceId(model))
}

// Save 单条新增，会自动忽略空值字段，forceUpdateCols表示强制插入字段，如果是主键为零值，会自动更新数据库主键值
func (my *BaseDao[Model]) save(model *Model, forceUpdateCols ...string) {
	if model != nil {
		engine := my.child().GetEngine()
		forceUpdateCols = my.getNotOmitFields(forceUpdateCols)
		omitCols := toolkit.GetZeroFieldNames4CustomStrategy(model,
			my.getEngine().GetColumnMapper().Obj2Table, forceUpdateCols...)
		_, err := engine.Omit(omitCols...).Insert(model)
		if err != nil {
			my.log().Error("新增失败: %v", err)
			panic(errors.New("新增失败"))
		}
	}
}

func (my *BaseDao[Model]) Save(model *Model, forceUpdateCols ...string) {
	my.save(model, forceUpdateCols...)
}

// SaveBatch 自动主键赋值批量插入方式，实际是一条一条插入，性能可能会稍有影响
func (my *BaseDao[Model]) SaveBatch(models []*Model, forceUpdateCols ...string) {
	for _, model := range models {
		my.save(model, forceUpdateCols...)
	}
}

// SaveBatchNotAssignId 非自动主键赋值批量插入方式
// 插入同一个表的多条数据，此时如果数据库支持批量插入，那么会进行批量插入，
// 但是这样每条记录就无法被自动赋予id值。
// 如果数据库不支持批量插入，那么就会一条一条插入。
func (my *BaseDao[Model]) SaveBatchNotAssignId(models []*Model, forceUpdateCols ...string) {
	if len(models) > 0 {
		engine := my.child().GetEngine()
		forceUpdateCols = my.getNotOmitFields(forceUpdateCols)
		omitCols := toolkit.GetZeroFieldNames4CustomStrategy(models[0],
			my.getEngine().GetColumnMapper().Obj2Table, forceUpdateCols...)
		_, err := engine.Omit(omitCols...).Insert(&models)
		if err != nil {
			my.log().Error("批量新增失败: %v", err)
			panic(errors.New("批量新增失败"))
		}
	}
}

// ================================= 删除 =================================

func (my *BaseDao[Model]) Delete(wrapper *wrappers.Wrapper[Model]) {
	if wrapper == nil {
		// 条件为空，就会全部删除，高危操作，不允许
		// 如果真的想全部删除，可以传入空条件 wrappers.Query[Model]()
		return
	}
	engine := my.child().GetEngine()
	_, err := engine.Where(wrapper.GetCond()).Unscoped().Delete(new(Model))
	if err != nil {
		my.log().Error("删除失败: %v", err)
		panic(errors.New("删除失败"))
	}
}

func (my *BaseDao[Model]) DeleteById(id any) {
	engine := my.child().GetEngine()
	_, err := engine.ID(id).Unscoped().Delete(new(Model))
	if err != nil {
		my.log().Error("根据主键删除记录失败: %v", err)
		panic(errors.New("根据主键删除记录失败"))
	}
}

func (my *BaseDao[Model]) DeleteBatchIds(ids ...any) {
	engine := my.child().GetEngine()
	_, err := engine.In(my.getPrimaryKeyCol(), ids...).
		NoAutoCondition(). // model 不自动生成where条件（禁用自动根据结构体中的值来生成条件）
		Unscoped().
		Delete(new(Model))
	if err != nil {
		my.log().Error("根据主键批量删除失败: %v", err)
		panic(errors.New("根据主键批量删除失败"))
	}
}

func (my *BaseDao[Model]) SaveOrUpdate(model *Model, forceUpdateCols ...string) {
	if model == nil {
		return
	}
	id := Model.GetId(*model)
	if id == 0 {
		my.save(model, forceUpdateCols...)
	} else {
		my.updateById(model, forceUpdateCols...)
	}
}

// ================================= 修改 =================================

func (my *BaseDao[Model]) updateById(model *Model, forceUpdateCols ...string) {
	// 通过泛型类型和泛型实例获取id，这在java中要简单很多，直接model.GetId()即可
	// 这里可以通过泛型参数Model.GetId，也可以使用base.Domain.GetId，注意：参数需要传实例对象或实例对象的指针，具体传啥需要看定义的接收者是对象还是指针
	id := base.Domain.GetId(*model)
	engine := my.child().GetEngine()
	// 168行的Cols方法，未指定的字段将不更新，所以当forceUpdateCols不为空时，要自己找出非0和非nil得值的字段，添加进去
	//if len(forceUpdateCols) > 0 {
	//	needUpdCols := toolkit.GetNotZeroFieldNames4CustomStrategy(model, engine.GetColumnMapper().Obj2Table)
	//	for _, col := range needUpdCols {
	//		if !slices.Contains(forceUpdateCols, col) {
	//			forceUpdateCols = append(forceUpdateCols, col)
	//		}
	//	}
	//}
	_, err := engine.ID(id).
		// Update会自动从 model 结构体中提取非0和非nil得值作为需要更新的内容
		//// 通过添加Cols函数指定需要更新结构体中的哪些值，未指定的将不更新，指定了的即使为0也会更新
		//Cols(forceUpdateCols...).
		MustCols(forceUpdateCols...). // 指定了的强制更新，未指定的根据结构体字段是否为0值决定是否更新
		Update(model)
	if err != nil {
		my.log().Error("根据主键修改失败: %v", err)
		panic(errors.New("根据主键修改失败"))
	}
}

func (my *BaseDao[Model]) UpdateById(model *Model, forceUpdateCols ...string) {
	my.updateById(model, forceUpdateCols...)
}

func (my *BaseDao[Model]) Update(model *Model, wrapper *wrappers.Wrapper[Model], forceUpdateCols ...string) {
	if wrapper == nil {
		return
	}
	engine := my.child().GetEngine()
	var err error
	if model == nil {
		setMap := wrapper.GetSetMap()
		if len(setMap) == 0 {
			return
		}
		_, err = engine.Table(new(Model)).Where(wrapper.GetCond()).Update(setMap)
	} else {
		_, err = engine.MustCols(forceUpdateCols...).Where(wrapper.GetCond()).Update(model)
	}
	if err != nil {
		my.log().Error("根据条件修改失败: %v", err)
		panic(errors.New("根据条件修改失败"))
	}
}

func (my *BaseDao[Model]) UpdateMap(updateMap map[string]any, wrapper *wrappers.Wrapper[Model]) {
	if len(updateMap) == 0 || wrapper == nil {
		return
	}
	engine := my.child().GetEngine()
	_, err := engine.Table(new(Model)).Where(wrapper.GetCond()).Update(updateMap)
	if err != nil {
		my.log().Error("根据条件修改失败: %v", err)
		panic(errors.New("根据条件修改失败"))
	}
}

// ================================= 查询 =================================

func getDefaultAndCond4Select(model base.Domain) builder.Cond {
	cond := base.Domain.DefaultAndCond4Select(model)
	if cond == nil {
		cond = builder.NewCond()
	}
	return cond
}

func (my *BaseDao[Model]) SelectById(id int64) *Model {
	if id == 0 {
		return nil
	}
	model := new(Model)
	engine := my.child().GetEngine()
	_, err := engine.ID(id).Where(getDefaultAndCond4Select(*model)).Get(model)
	if err != nil {
		my.log().Error("xormplus ==> SelectById error: %v", err)
		return nil
	}
	if toolkit.IsNil(*model) {
		// 判断零值
		return nil
	}
	return model
}

func (my *BaseDao[Model]) SelectByIds(ids []int64) []*Model {
	if len(ids) <= 0 {
		return nil
	}

	engine := my.child().GetEngine()
	b := wrappers.Query[Model]().
		In(my.getPrimaryKeyCol(), ids).
		PutCond(getDefaultAndCond4Select(*new(Model))).
		GetBuilder(my.getEngine())

	models := make([]*Model, 0)
	err := engine.SQL(b).Find(&models)
	if err != nil {
		my.log().Error("xormplus ==> SelectByIds error: %v", err)
	}
	return models
}

func (my *BaseDao[Model]) SelectCount(wrapper *wrappers.Wrapper[Model]) int64 {
	if wrapper == nil {
		wrapper = wrappers.Query[Model]()
	}
	modelInstance := new(Model)
	engine := my.child().GetEngine()
	count, err := engine.Where(wrapper.GetCond().And(getDefaultAndCond4Select(*modelInstance))).Count(modelInstance)
	if err != nil {
		my.log().Error("xormplus ==> SelectCount error: %v", err)
		return 0
	}
	if count < 1 {
		return 0
	}
	return count
}

func (my *BaseDao[Model]) SelectList(wrapper *wrappers.Wrapper[Model]) []*Model {
	models := make([]*Model, 0)
	if wrapper == nil {
		wrapper = wrappers.Query[Model]()
	}
	engine := my.child().GetEngine()
	wrapper.PutCond(getDefaultAndCond4Select(*new(Model)))
	err := engine.SQL(wrapper.GetBuilder(my.getEngine())).Find(&models)
	if err != nil {
		my.log().Error("xormplus ==> SelectList error: %v", err)
	}
	return models
}

func (my *BaseDao[Model]) SelectOne(wrapper *wrappers.Wrapper[Model]) *Model {
	model := new(Model)
	if wrapper == nil {
		wrapper = wrappers.Query[Model]()
	}
	engine := my.child().GetEngine()
	wrapper.PutCond(getDefaultAndCond4Select(*model))
	_, err := engine.SQL(wrapper.GetBuilder(my.getEngine())).Get(model)
	if err != nil {
		my.log().Error("xormplus ==> SelectOne error: %v", err)
		return nil
	}
	if toolkit.IsNil(*model) {
		// 判断零值
		return nil
	}
	return model
}

func (my *BaseDao[Model]) SelectMaps(wrapper *wrappers.Wrapper[Model]) []map[string]any {
	if wrapper == nil {
		wrapper = wrappers.Query[Model]()
	}
	engine := my.child().GetEngine()
	wrapper.PutCond(getDefaultAndCond4Select(*new(Model)))
	results, err := engine.QueryInterface(wrapper.GetBuilder(my.getEngine()))
	if err != nil {
		my.log().Error("xormplus ==> SelectMaps error: %v", err)
	}
	return results
}

func (my *BaseDao[Model]) SelectPage(page IPage[Model], wrapper *wrappers.Wrapper[Model]) IPage[Model] {
	if page == nil {
		page = NewDefaultPage[Model]()
	}
	if wrapper == nil {
		wrapper = wrappers.Query[Model]()
	}
	start := page.Start()
	limit := page.Limit()
	models := make([]*Model, 0, limit)
	modelInstance := new(Model)
	engine := my.child().GetEngine()

	count, err := engine.
		Where(wrapper.GetCond().And(getDefaultAndCond4Select(*modelInstance))).
		Count(modelInstance)
	if err != nil {
		my.log().Error("xormplus ==> SelectPage count query error: %v", err)
		return page
	}
	if count <= 0 {
		return page
	}
	page.SetTotal(count)

	session := engine.Cols(wrapper.GetSelect()...).
		Where(wrapper.GetCond().And(getDefaultAndCond4Select(*modelInstance))).
		Limit(limit, start)

	if wrapper.GetOrderBy() != nil {
		session.OrderBy(wrapper.GetOrderBy())
	}

	err = session.Find(&models)
	if err != nil {
		my.log().Error("xormplus ==> SelectPage page query error: %v", err)
		return page
	}
	page.SetRecords(models)
	return page
}

func (my *BaseDao[Model]) getPrimaryKeyCol() string {
	tableInfo := my.getTableInfo()
	if tableInfo == nil {
		return base.IdColumnName
	}
	if tableInfo.AutoIncrement != "" {
		return tableInfo.AutoIncrement
	}
	if len(tableInfo.PrimaryKeys) <= 0 {
		return base.IdColumnName
	}
	return tableInfo.PrimaryKeys[0]
}

func (my *BaseDao[Model]) getTableInfo() *schemas.Table {
	var model Model
	tableInfo, err := my.getEngine().TableInfo(&model)
	if err != nil {
		my.log().Error("xormplus ==> getTableInfo error: %v", err)
		return nil
	}
	return tableInfo
}

func (my *BaseDao[Model]) getNotOmitFields(forceUpdateCols []string) []string {
	tableInfo := my.getTableInfo()
	if tableInfo == nil {
		return forceUpdateCols
	}

	var autoForceUpdCols []string
	autoForceUpdCols = append(autoForceUpdCols, tableInfo.PrimaryKeys...)
	autoForceUpdCols = append(autoForceUpdCols, tableInfo.Updated)
	for col := range tableInfo.Created {
		autoForceUpdCols = append(autoForceUpdCols, col)
	}

	for _, colName := range autoForceUpdCols {
		if !slices.Contains(forceUpdateCols, colName) {
			forceUpdateCols = append(forceUpdateCols, colName)
		}
	}
	return forceUpdateCols
}
